/* Gyroe 1.0.0 */
/* fe_symtab.c : Add entries to the Symbol Table, calculate
   hashes and find things inside symtab.*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "fe_shared.h"
#include "fe_symtab.h"

int hashfn(char *name)
{
    int len, val;

    len = strlen(name);
    val = (name[len] * 256) + (name[0] * 2 + len);

    if (len >= 3) {
        val = val + (name[2] * 64);
        if (len >= 4)
            val = val + (name[3] * 64);
    }
 
    return (val % 2003) + 1;
}

int new_entry(char *name, int varnum, int scope, int token, int hashval, int samehash, int type_sym)
{
    st_extra--; st_pos++;
    if (st_extra == -1) {
        symtab = (long **)realloc(symtab, (st_pos+64) * sizeof (long *));
        st_extra = 63;
    }

    name_area_extra--; name_area_pos++;
    if (name_area_extra == -1) {
        name_area = (char **)realloc(name_area, (name_area_pos+8) * sizeof (char *));
        name_area_extra = 7;
    }

    symtab[st_pos] = malloc(16 * sizeof (long));

    symtab[st_pos][S_OBJ] = NOVALUE;
    symtab[st_pos][S_NEXT] = 0;
    symtab[st_pos][S_MODE] = M_NORMAL;
    symtab[st_pos][S_SCOPE] = scope;

    if (OpWarning)
        symtab[st_pos][S_USAGE] = U_VAR_UNUSED;
    else
        /* Avoid warnings by pretending it's been read and written to already. */
        symtab[st_pos][S_USAGE] = U_VAR_RW;

    symtab[st_pos][S_FILE_NO] = current_file_no;

    /* Append name to name_area and point to it in S_NAME. */
    name_area[name_area_pos] = malloc(strlen(name) + 1 * sizeof (char));
    strcpy(name_area[name_area_pos], name);

    symtab[st_pos][S_NAME] = name_area_pos;
    symtab[st_pos][S_SAMEHASH] = samehash;
    symtab[st_pos][S_TOKEN] = token;
    symtab[st_pos][S_HASHVAL] = hashval;
    symtab[st_pos][S_VARNUM] = varnum;
    symtab[st_pos][S_INITLEVEL] = -1;
    symtab[st_pos][S_VTYPE] = type_sym;
    symtab[st_pos][S_CODE] = ST_END;

    symtab[last_sym][S_NEXT] = st_pos;

    last_sym = st_pos;
    return last_sym;
}

int new_temp()
{
    st_extra--; st_pos++;
    if (st_extra == -1) {
        symtab = (long **)realloc(symtab, (st_pos+64) * sizeof (long *));
        st_extra = 63;
    }

    symtab[st_pos] = malloc(8 * sizeof(long));

    symtab[st_pos][S_OBJ] = NOVALUE;
    symtab[st_pos][S_NEXT] = 0;
    symtab[st_pos][S_MODE] = 0;
    symtab[st_pos][S_SCOPE] = 0;
    symtab[st_pos][S_USAGE] = SC_FREE;
    symtab[st_pos][S_FILE_NO] = ST_END;

    return st_pos;
}

void is_defined(int sym)
{
    int scope;

    scope = symtab[sym][S_SCOPE];

    if (scope != SC_UNDEFINED && scope != SC_MULTIPLY_DEFINED && scope != SC_PREDEF) {
        if (symtab[sym][S_FILE_NO] == current_file_no) {
            sprintf(fe_buffer, "Attempt to redefine %s", name_area[symtab[sym][S_NAME]]);
            compile_error(fe_buffer);
        }
    }
}

char * fe_name_ext(char *name)
{
    char *sym;
    int diff, i, j, len;

    len = strlen(name);

    for (i = len;i >= 0;i--) {
        if (name[i] == OS_SLASH) {
            i++; /* Leave the slash. */
            break;
        }
    }

    if (i == 0) {
        sprintf(fe_buffer, "fe_name_ext failed, name is `%s`.\n", name);
        internal_error(fe_buffer);
    }

    diff = len - i;
    sym = malloc(len - i + 1 * sizeof(char));

    for (j = 0;j < diff;j++, i++)
        sym[j] = name[i];

    sym[diff] = '\0';

    return sym;
}

int new_int_sym(int val)
{
    int i;

    /* lastintsym and lastintval are lined up (add to both at same
       time) so, they share lastintpos as length. */

    if (lastint_pos > -1) {
        for (i = 0;i <= lastint_pos;i++) {
            if (val == lastintval[i])
                return lastintsym[i];
        }
    }

    new_temp();

    symtab[st_pos][S_OBJ] = val;
    symtab[st_pos][S_MODE] = M_CONSTANT;

    lastint_extra--; lastint_pos++;
    if (lastint_extra == -1) {
        lastintsym = (int *)realloc(lastintsym, (lastint_pos+32) * sizeof (int));
        lastintval = (int *)realloc(lastintval, (lastint_pos+32) * sizeof (int));
        lastint_extra = 31;
    }

    lastintval[lastint_pos] = val;
    lastintsym[lastint_pos] = st_pos;

    return st_pos;
}

int new_atom_sym(long d)
{
    new_temp();

    symtab[st_pos][S_OBJ] = d;
    symtab[st_pos][S_NEXT] = literal_init;
    symtab[st_pos][S_MODE] = M_CONSTANT;
    /* S_SCOPE and S_USAGE already done. */

    literal_init = st_pos;
    return st_pos;
}

int new_string_sym(char *a)
/* Make an s1_ptr and slide it into place. */
{
    new_temp();

    symtab[st_pos][S_OBJ] = NewString(a);
    symtab[st_pos][S_NEXT] = literal_init;
    symtab[st_pos][S_MODE] = M_CONSTANT;
    
    literal_init = st_pos;
    return st_pos;
}

int new_temp_sym()
{
    int p;

    p = symtab[currentsub][S_TEMPS];

    /* Search for a free temp. */
    while (p && symtab[p][S_SCOPE] != SC_FREE)
        p = symtab[p][S_NEXT];

    if (!p) {
        /* None found. Make a new one. */
        temps_allocated++;

        new_temp();

        p = st_pos;

        symtab[p][S_MODE] = M_TEMP;
        symtab[p][S_NEXT] = symtab[currentsub][S_TEMPS];
        symtab[currentsub][S_TEMPS] = p;
    }

    /* Found a temp or made a new one. Either
       way, we need to set the other fields for it. */

    symtab[p][S_OBJ] = NOVALUE;
    symtab[p][S_SCOPE] = SC_IN_USE;
    symtab[p][S_USAGE] = T_UNKNOWN;

    return p;
}

void init_symtab()
{
    int hashval, k, st_entry, sym, token;

    symtab = malloc(128 * sizeof(long *));
    st_extra = 127; st_pos = 0;

    symtab[0] = malloc(4 * sizeof(long));
    /* This is a dummy so the first symtab[last_sym][S_NEXT]
       assignment has somewhere to go. It will never be checked. */

    dup_globals = malloc(4 * sizeof(int));
    dup_globals_last = -1; dup_globals_max = 4;

    lastintsym = malloc(32 * sizeof(int));
    lastintval = malloc(32 * sizeof(int));
    lastint_extra = 32; lastint_pos = -1;

    name_area = malloc(32 * sizeof(char *));
    name_area_extra = 32; name_area_pos = -1;

    buckets = malloc(2003 * sizeof(int));

    last_sym = 0;
    literal_init = 0;
    w_file = NULL;

    for (k = 1;k < 2003;k++)
        buckets[k] = 0;

    /* Okay, now we can setup symtab. */
    for (k = 0;k <= 97;k++) {
        hashval = hashfn(keylist[k].k_name);
        st_entry = new_entry(keylist[k].k_name, 0, keylist[k].k_scope,
                             keylist[k].k_token, hashval, 0, 0);
        token = keylist[k].k_token;
        if (token == PROC || token == FUNC || token == TYPE) {
            /* Make it a routine entry. */
            symtab[st_entry] = (long *)realloc(symtab[st_entry], ST_ROUTINE * sizeof(long));

            symtab[st_entry][S_CODE] = -1;
            symtab[st_entry][S_NUM_ARGS] = keylist[k].k_num_args;
            symtab[st_entry][S_OPCODE] = keylist[k].k_opcode;
            symtab[st_entry][S_FIRSTLINE] = 0;
            symtab[st_entry][S_TEMPS] = 0;
            symtab[st_entry][S_STACK_SPACE] = 0;
        }

        if (!buckets[hashval])
            buckets[hashval] = st_entry;
        else {
            sym = buckets[hashval];

            while (symtab[sym][S_SAMEHASH])
                sym = symtab[sym][S_SAMEHASH];

            symtab[sym][S_SAMEHASH] = st_entry;
        }
    }

    file_start_sym = st_pos;

    /* Set S_TEMPS for _toplevel_, in case it gets checked. */
    symtab[st_entry][S_TEMPS] = 0;
    symtab[st_entry][S_CODE] = 0;
}

void keyfind(char *word, int file_no)
{
    int defined, dup, hashval, scope, st_ptr;
    int back_tok[2];

    dup = 0;
    hashval = hashfn(word);
    st_ptr = buckets[hashval];

    while (st_ptr) {
        if (!strcmp(word, name_area[symtab[st_ptr][S_NAME]])) {
            token_name = symtab[st_ptr][S_TOKEN];
            token_sym = st_ptr;

            if (file_no == -1) {
                scope = symtab[st_ptr][S_SCOPE];

                if (scope == SC_GLOBAL) {
                    if (current_file_no == symtab[st_ptr][S_FILE_NO])
                        return;

                    back_tok[1] = token_name;
                    back_tok[2] = token_sym;

                    if (dup == dup_globals_max) {
                        dup_globals = (int *)realloc(dup_globals, 
                                                     (dup_globals_max * 2) *
                                                     sizeof (int));
                        dup_globals_max *= 2;
                    }

                    dup_globals[dup] = st_pos;
                    /* dup is used instead of a _pos since a _pos would have
                       to be reset at the end of the function. */

                    dup++;
                    dup_globals_last = dup;
                }
                else if (scope == SC_LOCAL) {
                    if (current_file_no == symtab[st_ptr][S_FILE_NO])
                        return;
                }
                else if (scope == SC_PREDEF) {
                    if (!dup)
                        return;
                }
                else
                    return;
            }
            else {
                if (file_no == symtab[st_ptr][S_FILE_NO] &&
                     symtab[st_ptr][S_SCOPE] == SC_GLOBAL)
                    return;
            }
        }

        st_ptr = symtab[st_ptr][S_SAMEHASH];
    }

    /* Special case : Only one matching global. Assign token_name, token_sym to
       back_tok's values, NOT token. Otherwise, we may return a pointer to a local
       or private in another file. */
    if (dup == 1) {
        token_name = back_tok[1];
        token_sym = back_tok[2];
        return;
    }

    if (!dup)
        defined = SC_UNDEFINED;
    else
        defined = SC_MULTIPLY_DEFINED;

    token_name = VARIABLE;
    token_sym = new_entry(word, 0, defined, VARIABLE, hashval, buckets[hashval], 0);

    buckets[hashval] = token_sym;
}

static void lint_check(int sym)
{
    boolean place;
    char *file, *problem, *scope, *vtype;
    int u;

    u = symtab[sym][S_USAGE];

    if (u == U_VAR_RW)
        /* This one's fine. */
        return;

    if (w_file == NULL)
        w_file = fe_name_ext(user_files[user_files_pos]);

    if (symtab[sym][S_SCOPE] == SC_LOCAL) {
        place = 0;
        scope = "local";
    }
    else if (symtab[sym][S_SCOPE] == SC_PRIVATE) {
        scope = "private";
        place = 1;
    }
    else if (symtab[sym][S_SCOPE] == SC_GLOBAL) {
        place = 0;
        scope = "global";
    }

    if (symtab[sym][S_MODE] == M_CONSTANT)
        vtype = "constant";
    else {
        /* J.A. -> Assume just a normal variable. */
        vtype = "variable";
    }

    if (u == U_VAR_UNUSED)
        problem = "never used";
    else if (u == U_VAR_READ)
        problem = "is read, but never assigned a value";
    else if (u == U_VAR_WRITTEN)
        problem = "is written to, but the value is never read";

    if (place) {
        sprintf(fe_buffer, "Warning : %s %s %s in routine %s in %s is %s.\n", scope, vtype,
                name_area[symtab[sym][S_NAME]], name_area[symtab[currentsub][S_NAME]],
                w_file, problem);

        /* ex : private variable foo in routine bar in misc.e is not used. */
        add_warning(fe_buffer);
    }
    else {
        sprintf(fe_buffer, "Warning : %s %s %s in %s is %s.\n", scope, vtype,
                name_area[symtab[sym][S_NAME]], w_file, problem);

        /* ex : local variable foo in misc.e is not assigned a value. */
        add_warning(fe_buffer);
    }
}

void hide(int sym)
/* Hide a variable or parameter name. This allows
   the user to declare the name again. */
{
    int p, prev;

    p = buckets[symtab[sym][S_HASHVAL]];
    prev = 0;

    while (p && p != sym) {
        prev = p;
        p = symtab[p][S_SAMEHASH];
    }

    if (!p)
        return; /* Already been hidden. */

    if (!prev)
        buckets[symtab[sym][S_HASHVAL]] = symtab[sym][S_SAMEHASH];
    else
        symtab[prev][S_SAMEHASH] = symtab[sym][S_SAMEHASH];
}

void scope_exit()
/* Current routine is exiting. Hide all its vars.
   While we're at it, do some lint checking. */
{
    int sym;

    sym = symtab[currentsub][S_NEXT];

    while (sym && symtab[sym][S_SCOPE] == SC_PRIVATE) {
        hide(sym);
        lint_check(sym);
        sym = symtab[sym][S_NEXT];
    }
}

void hide_locals()
/* Exiting from a file. Hide the locals, and lint check. */
{
    int sym;

    sym = file_start_sym;

    while (sym) {
        /* Grab only the locals in this file. */
        if (symtab[sym][S_SCOPE] == SC_LOCAL && symtab[sym][S_FILE_NO] == current_file_no) {
            hide(sym);
            /* Note : Don't check for OpWarning being on, since it could have been turned
               off near the end of a file. Check everything regardless, since anything declared
               when it's off will have U_VAR_RW and be skipped. */
            if (symtab[sym][S_TOKEN] == VARIABLE)
                lint_check(sym);
        }
        sym = symtab[sym][S_NEXT];
    }

    /* Same issue here as with above. Don't check for warnings since it may have been
       on for part of the file, then turned off later on. */
    if (w_file != NULL) {
        free(w_file);
        w_file = NULL;
    }
}

